﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics;
using Microsoft.Xna.Framework;
using BEPUphysics.Entities;
using PloobsEngine.Physics.Bepu;
using PloobsEngine.Cameras;
using PloobsEngine.Physic.PhysicObjects.BepuObject;
using PloobsEngine.SceneControl;

namespace PloobsEngine.Physics
{    

    public class BepuPhysicWorld : IPhysicWorld
    {
        Space space;
        IDebugDrawn dd;
        private bool isDebug = false;
        private List<IPhysicObject> objs;

        public BepuPhysicWorld()
        {
            space = new Space();
            objs = new List<IPhysicObject>();
            space.SimulationSettings.MotionUpdate.Gravity = new Vector3(0, -9.81f, 0);            
        }

        public BepuPhysicWorld(IDebugDrawn dd)
        {
            isDebug  = true;
            this.dd = dd;
            space = new Space();
            objs = new List<IPhysicObject>();
            space.SimulationSettings.MotionUpdate.Gravity = new Vector3(0, -9.81f, 0);
        }
        public Space Space
        {
            get { return space; }
            set { space = value; }
        }        

        #region IPhysicWorld Members

        public bool isDebugDraw
        {
            get
            {
                return isDebug;
            }
            set
            {
                isDebug = value;
            }
        }

        public void Update(Microsoft.Xna.Framework.GameTime gt)
        {            
            space.Update(gt);
        }

        public void AddObject(IPhysicObject obj)
        {            
            if (obj.PhysicObjectTypes == PhysicObjectTypes.TRIANGLEMESHOBJECT)
            {
                TriangleMeshObject bo = (TriangleMeshObject)obj;
                bo.TriangleGroup.Tag = obj;
                space.Add(bo.TriangleGroup);
                objs.Add(obj);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.SPECIALIZEDMOVER)
            {
                ObjectMover m = (ObjectMover)obj;
                space.Add(m.BepuEntityObject.Entity);
                m.BepuEntityObject.Entity.Tag = obj;
                space.Add(m.Mover);
                space.Add(m.Rotator);
                objs.Add(m.BepuEntityObject);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.TERRAIN)
            {
                TerrainObject t = obj as TerrainObject;
                space.Add(t.Terrain);
                t.Terrain.Tag = obj;
                objs.Add(obj);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.DETECTOROBJECT)
            {
                DetectorVolumeObject m = (DetectorVolumeObject)obj;
                space.Add(m.DetectorVolume);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.CHARACTEROBJECT)
            {
                CharacterController cc = (CharacterController)obj;
                cc.Body.Tag = obj;
                space.Add(cc);
                objs.Add(obj);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.GHOST)
            {
                if (obj is AgregatedPhysicObject)
                {
                    objs.Add(obj);
                    AgregatedPhysicObject ag = obj as AgregatedPhysicObject;
                    foreach (var item in ag.PhysicsObjects)
                    {
                        item.ObjectOwner = obj.ObjectOwner;
                        this.AddObject(item);
                    }
                }
                else
                {
                    objs.Add(obj);
                }
            }
            else
            {
                BepuEntityObject bo = (BepuEntityObject)obj;
                bo.Entity.Tag = obj;
                space.Add(bo.Entity);
                objs.Add(obj);
            }
            
        }

        public void RemoveObject(IPhysicObject obj)
        {
            if (obj.PhysicObjectTypes == PhysicObjectTypes.TRIANGLEMESHOBJECT)
            {
                TriangleMeshObject bo = (TriangleMeshObject)obj;
                bo.TriangleGroup.Tag = null;
                space.Remove(bo.TriangleGroup);
                objs.Remove(obj);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.SPECIALIZEDMOVER)
            {
                ObjectMover m = (ObjectMover)obj;
                space.Remove(m.BepuEntityObject.Entity);
                m.BepuEntityObject.Entity.Tag = null;
                space.Remove(m.Mover);
                space.Remove(m.Rotator);
                objs.Remove(m.BepuEntityObject);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.DETECTOROBJECT)
            {
                DetectorVolumeObject m = (DetectorVolumeObject)obj;
                space.Remove(m.DetectorVolume);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.TERRAIN)
            {
                TerrainObject t = obj as TerrainObject;
                space.Remove(t.Terrain);
                t.Terrain.Tag = null;
                objs.Remove(obj);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.CHARACTEROBJECT)
            {
                CharacterController cc = (CharacterController)obj;
                cc.Body.Tag = null;
                space.Remove(cc);
                objs.Remove(obj);
            }
            else if (obj.PhysicObjectTypes == PhysicObjectTypes.GHOST)
            {
                if (obj is AgregatedPhysicObject)
                {
                    objs.Remove(obj);
                    AgregatedPhysicObject ag = obj as AgregatedPhysicObject;
                    foreach (var item in ag.PhysicsObjects)
                    {
                        item.ObjectOwner = null;
                        this.RemoveObject(item);
                    }
                }
                else
                {
                    objs.Remove(obj);
                }
            }
            else
            {
                BepuEntityObject bo = (BepuEntityObject)obj;
                bo.Entity.Tag = null;
                space.Remove(bo.Entity);
                objs.Remove(obj);
            }            
         
        }

        public void DebugDrawn(Microsoft.Xna.Framework.GameTime gt, ICamera cam)
        {           
            dd.Draw(gt, cam, this);
        }

        public void Serialize(System.Xml.XmlTextWriter textWriter)
        {
            throw new NotImplementedException();
        }

        public void DeSerialize(System.Xml.XmlNode node, EngineStuff engine)
        {
            throw new NotImplementedException();
        }

        public SegmentInterceptInfo SegmentIntersect(Microsoft.Xna.Framework.Ray raio, SegmentInterceptMethod method, float maxDistance)
        {
            List<BEPUphysics.Entities.Entity> ent = new List<BEPUphysics.Entities.Entity>();
            List<Vector3> loc = new List<Vector3>();
            List<Vector3> normal = new List<Vector3>();
            List<float> time = new List<float>();                        

            if (space.RayCast(raio.Position, raio.Direction, maxDistance, false, ent, loc, normal, time) == true)
            {
                int index = -1;
                foreach (var item in ent)
                {
                    index++;
                    if (item == null) ///provavelmente trianglemesh, problema q precisa corrigir, ja falei pro pessoal da bepu ....
                    {
                        if (method == SegmentInterceptMethod.NO_TRINAGLEMESH || method == SegmentInterceptMethod.ONLY_MOBILES)
                            continue;

                        ///Solucao intermediaria (lenta pra burro, mas funciona)
                        foreach (var tmeshes in objs.FindAll((a) => a.PhysicObjectTypes == PhysicObjectTypes.TRIANGLEMESHOBJECT))
                        {
                            TriangleMeshObject t = tmeshes as TriangleMeshObject;
                            List<Vector3> hitlocs = new List<Vector3>();
                            List<Vector3> hitnorms = new List<Vector3>();
                            List<float> times = new List<float>();
                            if (t.TriangleMesh.RayCast(raio.Position, raio.Direction, maxDistance, hitlocs, hitnorms, times, true))
                            {
                                SegmentInterceptInfo sin = new SegmentInterceptInfo();
                                sin.Distance = Vector3.Distance(raio.Position, hitlocs[0]);
                                sin.ImpactNormal = hitnorms[0];
                                sin.ImpactPosition = hitlocs[0];
                                sin.PhysicObject = t;
                                return sin;
                            }
                        }
                        ///Se nao encontrou o triangle mesh, entao nao houve picking !!!!
                        ///isto pode nao ser o ideal, mas acredito q seja melhor q devolver null no objeto ....
                        return null;
                    }
                    else
                    {
                        if (method == SegmentInterceptMethod.ONLY_TRIANGLEMESH)
                            continue;

                        if (method == SegmentInterceptMethod.ONLY_MOBILES && item.IsDynamic == false)
                            continue;
                        else if (method == SegmentInterceptMethod.ONLY_STOPPEDS && item.IsDynamic == true)
                            continue;

                        SegmentInterceptInfo si = new SegmentInterceptInfo();
                        si.Distance = Vector3.Distance(raio.Position, loc[index]);
                        si.ImpactNormal = normal[index];
                        si.ImpactPosition = loc[index];
                        if (ent != null)
                            si.PhysicObject = ent[index].Tag as IPhysicObject;
                        return si;
                    }
                    
                }                   
            }
            return null;
            
        }

        public List<IPhysicObject> DetectCollisions(IPhysicObject po)
        {
            List<IPhysicObject> col = new List<IPhysicObject>();
            BepuEntityObject bo = (BepuEntityObject)po;
            foreach (var item in bo.Entity.CollisionPairs)
	        {
                col.Add(item.ColliderB.Tag as IPhysicObject);
	        }            
            return col;
        }

        public List<IPhysicObject> GetPhysicsObjectsInRange(IPhysicObject po, float distance, CullerConditionAvaliator<IPhysicObject, IObject> condition)
        {
            List<BEPUphysics.Entities.Entity> ent = new List<BEPUphysics.Entities.Entity>();
            List<IPhysicObject> resp = new List<IPhysicObject>();
            space.BroadPhase.GetEntities(new BoundingSphere(po.Position,distance),ent);
            foreach (var item in ent)
            {
                IPhysicObject phyObj = item.Tag as IPhysicObject;
                if (condition(phyObj,phyObj.ObjectOwner))
                {
                    resp.Add(phyObj);
                }
            }
            return resp;
        }

        public List<IPhysicObject> GetPhysicsObjectsInRange(BoundingFrustum frustrum, CullerConditionAvaliator<IPhysicObject,IObject> condition)
        {
            List<BEPUphysics.Entities.Entity> ent = new List<BEPUphysics.Entities.Entity>();
            List<IPhysicObject> resp = new List<IPhysicObject>();
            space.BroadPhase.GetEntities(frustrum, ent);
            foreach (var item in ent)
            {
                IPhysicObject phyObj = item.Tag as IPhysicObject;
                if (condition(phyObj,phyObj.ObjectOwner))
                {
                    resp.Add(phyObj);
                }
            }
            return resp;
        }

        public List<IObject> GetIObjectsInRange(BoundingFrustum frustrum, CullerConditionAvaliator<IPhysicObject, IObject> condition)
        {
            List<BEPUphysics.Entities.Entity> ent = new List<BEPUphysics.Entities.Entity>();
            List<IObject> resp = new List<IObject>();
            space.BroadPhase.GetEntities(frustrum, ent);
            foreach (var item in ent)
            {
                IPhysicObject phyObj = item.Tag as IPhysicObject;
                if (condition(phyObj, phyObj.ObjectOwner))
                {
                    resp.Add(phyObj.ObjectOwner);
                }
            }
            return resp;
        }


        public void AddUpdateableObject(Updateable updateable ) 
        {            
            space.Add(updateable);
        }

        public void RemoveUpdateableObject(Updateable updateable) 
        {
            space.Remove(updateable);
        }

        #endregion

        #region IPhysicWorld Members


        public List<IPhysicObject> PhysicObjects
        {
            get { return objs; }
        }

        #endregion
    }
}
